<?php
namespace Model;

use Core\HHO;
use Lib\Http\Form, Lib\Http\Cookie, Lib\Http\Request, Exception;
use Dao\Rules as DRules, Dao\Session as DSession;

class Reserve {
	/**
	 * 未登录远程或远程会话已超时。
	 * @var int
	 */
	const ERROR_SESS_INVALID = 500;
	/**
	 * 当前会话ID（非 12306 会话）。
	 * @var string
	 */
	public $sid = NULL;
	/**
	 * 12306 的坐席类别索引。
	 * @var array
	 */
	public static $seat = ['1' => '硬座', '3' => '硬卧', '4' => '软卧', 'M' => '一等座', 'O' => '二等座'];
	/**
	 * 12306 注册的用户列表。
	 * @var array
	 */
	public static $user = ['yulu588' => ['李沙沙', 'susisoy'], 'susisoy' => ['侯国全', 's12343141524'], 'mygoupiao1' => ['王小鑫', 'susisoy1'], 'mygoupiao2' => ['王铮', 'susisoy1'], 'mygoupiao3' => ['黄金生', 'susisoy1'], 'mygoupiao4' => ['李娜', 'susisoy1'], 'mygoupiao5' => ['王泰山', 'susisoy1']];
	/**
	 * 当前会话使用的用户。
	 * @var string
	 */
	private $uname = NULL;
	private $DSession = NULL;
	private $Cookie = NULL;
	private $Request = NULL;

	private function __construct($sid) {
		$this->sid = $sid;
		
		$this->DSession = DSession::getInstance($this->sid);
		
		$this->Cookie = Cookie::getInstance('http://dynamic.12306.cn/', $this->sid);
		$this->Cookie->option(Cookie::OPT_STORAGE, Cookie::COOKIE_STORAGE_FILES);
		
		$this->Request = new Request();
		$this->Request->option(Request::OPT_TIMEOUT, 10);
		$this->Request->option(Request::OPT_MODE, Request::OPT_MODE_CURL);
	}

	private function errApi($Response) {
		if (!is_object($Response) || $Response->status() != '200') {
			throw new Exception('无法访问远程');
		}
		
		if (strpos($Response, '系统维护中')) {
			throw new Exception('远程系统维护中');
		}
		
		if (strpos($Response, '系统忙')) {
			throw new Exception('远程系统忙');
		}
		
		if ($Response == '-10') {
			throw new Exception('远程用户未登录', self::ERROR_SESS_INVALID);
		}
		
		if (strpos($Response, '输入正确的验证码') || strpos($Response, '验证码不正确')) {
			throw new Exception('验证码错误');
		}
		
		if (strpos($Response, '访问用户过多，请稍后再试')) {
			throw new Exception('远程系统当前访问用户过多');
		}
		
		if (strpos($Response, '非法的订票请求') !== FALSE) {
			throw new Exception('远程非法访问');
		}
	}

	private function record($action, $html) {
		file_put_contents($this->files = HHO::$config['runtime.root'] . '/12306.txt', $action . "\r\n\r\n" . trim($html) . "\r\n\r\n", FILE_APPEND);
	}

	private function _toStatus($status) {
		$order = $this->DSession->getOrder();
		if (!$order) {
			return FALSE;
		}
		
		$DRules = DRules::getInstance();
		
		foreach ($order['rule'] as $rid => $x) {
			$DRules->toStatus($rid, $status);
		}
		
		$status == DRules::STATUS_LOCK || $this->DSession->setOrder();
		
		return TRUE;
	}

	private function _getFilter($filter = NULL) {
		if (!$this->DSession) {
			return [];
		}
		
		$list = [];
		
		if (is_array($filter)) {
			foreach ($filter as $item) {
				$item = trim($item);
				if (!$item) {
					continue;
				}
				
				$x = explode(':', $item);
				
				$no = trim($x[0]);
				$seat = isset($x[1]) ? trim($x[1]) : '-';
				
				isset($list[$no][$seat]) || $list[$no][$seat] = TRUE;
			}
		}
		
		$xFilter = $this->DSession->getFilter();
		foreach ($xFilter as $no => $seats) {
			foreach ($seats as $seat => $x) {
				isset($list[$no][$seat]) || $list[$no][$seat] = TRUE;
			}
		}
		
		return $list;
	}

	/**
	 * 初始化会话。
	 * 
	 * @param string $from
	 * @param string $to
	 * @param string $dater
	 * @return boolean
	 */
	public function init($from, $to, $dater) {
		$station = json_decode(file_get_contents(HHO::$config['data.root'] . '/Station.json'), TRUE);
		if (!isset($station[$from]) || !isset($station[$to])) {
			throw new Exception('未知的车站名称');
		}
		
		if (!preg_match('/^\d{4}\-\d{2}\-\d{2}$/', $dater)) {
			throw new Exception('错误的出行日期');
		}
		
		$this->Cookie->destruct();
		$this->DSession->destruct();
		
		$Response = $this->Request->post('https://dynamic.12306.cn/otsweb/', NULL, $this->Cookie);
		$this->errApi($Response);
		
		if (!$this->DSession->setTicket($from, $to, $dater)) {
			throw new Exception('无法维持会话');
		}
	}

	public function getLoginCode() {
		$Response = $this->Request->get('https://dynamic.12306.cn/otsweb/passCodeAction.do?rand=sjrand', NULL, $this->Cookie);
		if ($Response->status() == '200') {
			return strval($Response);
		}
		
		return file_get_contents(HHO::$config['data.root'] . '/Code.png');
	}

	public function login($user, $code) {
		$ticket = $this->DSession->getTicket();
		if (!isset($ticket['from'])) {
			throw new Exception('无效的远程会话', self::ERROR_SESS_INVALID);
		}
		
		$Response = $this->Request->post('https://dynamic.12306.cn/otsweb/loginAction.do?method=loginAysnSuggest', NULL, $this->Cookie);
		$this->errApi($Response);
		
		$json = json_decode($Response, TRUE);
		if (!isset($json['loginRand'])) {
			$this->record('LID', $Response);
			
			throw new Exception('无法解析登录码');
		}
		
		$Form = new Form();
		$Form->set('loginRand', $json['loginRand']);
		$Form->set('loginUser.user_name', $user);
		$Form->set('nameErrorFocus', '');
		$Form->set('passwordErrorFocus', '');
		$Form->set('randCode', $code);
		$Form->set('randErrorFocus', '');
		$Form->set('refundFlag', 'Y');
		$Form->set('refundLogin', 'N');
		$Form->set('user.password', self::$user[$user][1]);
		
		$Response = $this->Request->post('https://dynamic.12306.cn/otsweb/loginAction.do?method=login', $Form, $this->Cookie);
		$this->errApi($Response);
		
		if (strpos($Response, '访问用户过多，请稍后重试')) {
			throw new Exception('远程系统当前访问用户过多');
		}
		
		if (!preg_match('/<h1 class\="text_16">([^<]+)<\/h1>/', $Response, $x)) {
			$this->record('LOGIN', $Response);
			
			throw new Exception('无法分析欢迎信息');
		}
		
		if (!$this->DSession->setUser($user)) {
			throw new Exception('无法设置远程用户');
		}
		
		return trim($x[1]);
	}

	public function detect($filter = NULL) {
		$ticket = $this->DSession->getTicket();
		if (!isset($ticket['from'])) {
			throw new Exception('远程用户未登录', self::ERROR_SESS_INVALID);
		}
		
		$DRules = DRules::getInstance();
		$restore = $this->DSession->getOrder();
		if (isset($restore['rule'])) {
			foreach ($restore['rule'] as $rid => $x) {
				$DRules->toStatus($rid, DRules::STATUS_INIT, DRules::STATUS_LOCK);
			}
			
			$this->DSession->setOrder();
		}
		
		$station = json_decode(file_get_contents(HHO::$config['data.root'] . '/Station.json'), TRUE);
		
		// =====================================================================
		$Form = new Form();
		$Form->set('orderRequest.train_date', $ticket['dater']);
		$Form->set('orderRequest.from_station_telecode', $station[$ticket['from']]);
		$Form->set('orderRequest.to_station_telecode', $station[$ticket['to']]);
		$Form->set('orderRequest.train_no', '');
		$Form->set('trainPassType', 'QB');
		$Form->set('trainClass', 'QB#D#Z#T#K#QT#');
		$Form->set('includeStudent', '00');
		$Form->set('seatTypeAndNum', '');
		$Form->set('orderRequest.start_time_str', '00:00--24:00');
		
		$Response = $this->Request->post('https://dynamic.12306.cn/otsweb/order/querySingleAction.do?method=queryLeftTicket', $Form, $this->Cookie);
		$this->errApi($Response);
		
		preg_match_all('/([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),<input[^>]+getSelected\(\'([^\)]+)\'\)/i', $Response, $data);
		$data = array_slice($data, 1);
		
		$length = count($data) - 1;
		$filter = $this->_getFilter($filter);
		
		$seat = ['商务座', '特等座', '一等座', '二等座', '高级软卧', '软卧', '硬卧', '软座', '硬座', '无座', '其他'];
		
		$list = [];
		foreach (end($data) as $offset => $item) {
			$item = explode('#', $item);
			
			if (isset($filter[$item[0]]['-'])) { // 某车次被忽略。
				continue;
			}
			
			$train = [];
			$train['no'] = $item[0];
			$train['lishi'] = $item[1];
			$train['start'] = $item[2];
			$train['trainno'] = $item[3];
			$train['fromCode'] = $item[4];
			$train['toCode'] = $item[5];
			$train['arrive'] = $item[6];
			$train['from'] = $item[7];
			$train['to'] = $item[8];
			$train['yp'] = $item[9];
			$train['mm'] = $item[10];
			
			for ($i = 0; $i < $length; $i++) {
				if (isset($filter[$train['no']][$seat[$i]])) { // 某类席位被忽略。
					continue;
				}
				
				if (preg_match('/>(.+)</', $data[$i][$offset], $x)) {
					if ($x[1] == '有') {
						$train[$seat[$i]] = PHP_INT_MAX;
					} elseif ($x[1] != '无') {
						$train[$seat[$i]] = $x[1];
					}
				} elseif ($data[$i][$offset] != '--') {
					$train[$seat[$i]] = $data[$i][$offset];
				}
			}
			
			$list[$train['no']] = $train;
		}
		
		// =====================================================================
		$target = Rules::getInstance()->passenger($list, $ticket['from'], $ticket['to'], $ticket['dater']);
		if ($target) {
			foreach ($list[$target['no']] as $k => $v) {
				$target[$k] = $v;
			}
			$this->DSession->setOrder($target);
			
			$this->_toStatus(DRules::STATUS_LOCK);
			
			$info = ['no' => $target['no'], 'passenger' => []];
			foreach ($target['rule'] as $rid => $rule) {
				foreach ($rule['passenger'] as $item) {
					$info['passenger'][$item[0]] = $rule['seat'];
					$info[$rule['seat']] = $target[$rule['seat']];
				}
			}
			return $info;
		}
		
		return [];
	}

	public function getReserveCode() {
		$Response = $this->Request->get('https://dynamic.12306.cn/otsweb/passCodeAction.do?rand=randp', NULL, $this->Cookie);
		if ($Response->status() == '200') {
			return strval($Response);
		}
		
		return file_get_contents(HHO::$config['data.root'] . '/Code.png');
	}

	public function reserve($code) {
		$order = $this->DSession->getOrder();
		if (!$order) {
			throw new Exception('未初始化预订');
		}
		
		$ticket = $this->DSession->getTicket();
		
		$Form = new Form();
		$Form->set('station_train_code', $order['no']);
		$Form->set('train_date', $ticket['dater']);
		$Form->set('seattype_num', '');
		$Form->set('from_station_telecode', $order['fromCode']);
		$Form->set('to_station_telecode', $order['toCode']);
		$Form->set('include_student', '00');
		$Form->set('from_station_telecode_name', $order['from']);
		$Form->set('to_station_telecode_name', $order['to']);
		$Form->set('round_train_date', date('Y-m-d'));
		$Form->set('round_start_time_str', '00:00--24:00');
		$Form->set('single_round_type', '1');
		$Form->set('train_pass_type', 'QB');
		$Form->set('train_class_arr', 'QB#D#Z#T#K#QT#');
		$Form->set('start_time_str', '00:00--24:00');
		$Form->set('lishi', $order['lishi']);
		$Form->set('train_start_time', $order['start']);
		$Form->set('trainno4', $order['trainno']);
		$Form->set('arrive_time', $order['arrive']);
		$Form->set('from_station_name', $order['from']);
		$Form->set('to_station_name', $order['to']);
		$Form->set('ypInfoDetail', $order['yp']);
		$Form->set('mmStr', $order['mm']);
		
		$Response = $this->Request->post('https://dynamic.12306.cn/otsweb/order/querySingleAction.do?method=submutOrderRequest', $Form, $this->Cookie);
		$Response = $this->Request->post('https://dynamic.12306.cn/otsweb/order/confirmPassengerAction.do?method=init', $Form, $this->Cookie);
		$this->errApi($Response);
		
		if (strpos($Response, '您还有未处理的订单')) {
			throw new Exception('用户有未处理的订单，远程限制再次预订');
		}
		
		$x = preg_replace('/^.+<form (.+)<\/form>.+$/is', '\\1', $Response);
		preg_match_all('/<input.+(name\=[^>]+)>/i', $x, $list);
		
		$input = [];
		foreach ($list[1] as $item) {
			preg_match('/name="(\S+)"/i', $item, $x);
			preg_match('/value="(\S+)"/i', $item, $y);
			
			$input[$x[1]] = isset($y[1]) ? $y[1] : '';
		}
		$input = array_intersect_key($input, ['org.apache.struts.taglib.html.TOKEN' => '', 'textfield' => '', 'leftTicketStr' => '', 'orderRequest.train_date' => '', 'orderRequest.train_no' => '', 'orderRequest.station_train_code' => '', 'orderRequest.from_station_telecode' => '', 'orderRequest.to_station_telecode' => '', 'orderRequest.seat_type_code=' => '', 'orderRequest.seat_detail_type_code=' => '', 'orderRequest.ticket_type_order_num=' => '', 'orderRequest.bed_level_order_num' => '', 'orderRequest.start_time' => '', 'orderRequest.end_time' => '', 'orderRequest.from_station_name' => '', 'orderRequest.to_station_name' => '', 'orderRequest.cancel_flag' => '', 'orderRequest.id_mode' => '', 'orderRequest.reserve_flag' => '']);
		
		// =====================================================================
		$Form = new Form();
		$Form->set($input);
		$Form->set('orderRequest.reserve_flag', 'A'); // 目前支付方式只支持“网上支付”，不支持“网上预订”；“网上支付”须在规定时间内完成支付，“网上预订”须在规定时间内取票。
		$Form->set('randCode', $code);
		
		$index = 0;
		$year = date('Y');
		
		foreach ($order['rule'] as $rid => $rule) {
			$seat = array_search($rule['seat'], self::$seat);
			
			foreach ($rule['passenger'] as $passenger) {
				$index++;
				
				if ($year - substr($passenger[1], 6, 4) > 50) { // 超过50岁，且为卧铺，则要求下铺。
					$detail = 1; // 下铺。
				} else {
					$detail = 0; // 任意。
				}
				
				$x = $seat . ',' . $detail . ',1,' . $passenger[0] . ',1,' . $passenger[1] . ',' . $passenger[2] . ',N';
				$Form->set('passengerTickets', $x, FALSE);
				$Form->set('passenger_' . $index . '_seat', $seat); // 席位类型。
				$Form->set('passenger_' . $index . '_seat_detail', $detail); // 卧铺位置。
				$Form->set('passenger_' . $index . '_ticket', 1); // 成人票。
				$Form->set('passenger_' . $index . '_name', $passenger[0]); // 旅客名称。
				$Form->set('passenger_' . $index . '_cardtype', 1); // 二代身份证。
				$Form->set('passenger_' . $index . '_cardno', $passenger[1]); // 身份证号。
				$Form->set('passenger_' . $index . '_mobileno', $passenger[2]); // 手机号。
			}
		}
		
		$Response = $this->Request->post('https://dynamic.12306.cn/otsweb/order/confirmPassengerAction.do?method=confirmSingleForQueueOrder', $Form, $this->Cookie);
		$this->errApi($Response);
		$result = json_decode($Response, TRUE);
		
		if (!isset($result['errMsg'])) {
			$this->record('RESERVE', $Response);
			
			throw new Exception('无法解析预订结果');
		}
		
		if ($result['errMsg'] == 'Y') { // 成功进入排队。
			return TRUE;
		}
		
		$this->_toStatus(DRules::STATUS_INIT);
		
		if ($result['errMsg'] == '包含排队中的订单') { // 12306 用户已在预订排队。
			throw new Exception('远程用户已在预订排队');
		} elseif ($result['errMsg'] == '包含未付款订单') { // 12306 用户名下存在有预订成功但未支付的订单。
			throw new Exception('远程用户存在未付订单');
		} elseif (strpos($result['errMsg'], '排队人数现已超过余票数') !== FALSE) { // 12306：排队人数现已超过余票数，请您选择其他席别或车次。
			foreach ($order['rule'] as $rid => $rule) {
				$this->DSession->addFilter($order['no'], $rule['seat']);
			}
			
			throw new Exception('余票不足');
		}
		
		throw new Exception($result['errMsg']);
	}

	public function order() {
		$order = $this->DSession->getOrder();
		if (!$order) {
			throw new Exception('未初始化预订');
		}
		
		$ticket = $this->DSession->getTicket();
		
		$Response = $this->Request->get("https://dynamic.12306.cn/otsweb/order/myOrderAction.do?method=getOrderWaitTime&tourFlag=dc&train_date=" . urlencode($ticket['dater']) . "&station={$order['no']}&seat=1&from=" . urlencode($order['fromCode']) . "&to=" . urlencode($order['toCode']), NULL, $this->Cookie);
		$this->errApi($Response);
		
		if (strpos($Response, '<title>登录</title>')) {
			throw new Exception('远程登录已注销', self::ERROR_SESS_INVALID);
		}
		
		$result = json_decode($Response, TRUE);
		
		if (isset($result['orderId'])) { // 成功预订，已生成支付订单。
			$this->_toStatus(DRules::STATUS_SUCC);
			
			return ['order' => $result['orderId']];
		} elseif (isset($result['msg']) && $result['msg']) { // 异常，预订失败。
			$this->_toStatus(DRules::STATUS_INIT);
			
			if (strpos($result['msg'], '没有足够的票') !== FALSE || strpos($result['msg'], '售票实行实名制')) {
				foreach ($order['rule'] as $rule) {
					$this->DSession->addFilter($order['no'], $rule['seat']);
				}
			}
			
			return ['error' => $result['msg']];
		} elseif (isset($result['requestId'])) { // 还在排队中。
			return ['time' => $result['waitTime'], 'count' => $result['count']];
		}
		
		$this->record('ORDER', $Response);
		
		throw new Exception('无法解析预订结果');
	}

	public function destruct() {
		$this->Cookie->destruct();
		$this->DSession->destruct();
	}

	public static function getInstance($sid) {
		static $instance = NULL;
		
		$instance || $instance = new self($sid);
		
		return $instance;
	}
}